//
//  ManifestDocument.m
//  iPhone Backup Slurper
//
//  Created by Bob Newhart on 10/17/08.
//  Copyright 2008 Geekspiff. All rights reserved.
//

#import "ManifestDocument.h"


@implementation ManifestDocument

- (void)_sha1String: (NSString *)sha1String toValues: (uint32_t *)sha1
{
	if ([sha1String hasPrefix: @"<"])
		sha1String = [sha1String substringFromIndex: 1];
	if ([sha1String hasSuffix: @">"])
		sha1String = [sha1String substringToIndex: [sha1String length] - 1];

	NSUInteger i;
	NSArray *components = [sha1String componentsSeparatedByString: @" "];
	if (5 == [components count]) {
		for (i = 0; i < 5; ++i) {
			NSScanner *scanner = [NSScanner scannerWithString:[components objectAtIndex: i]];
			BOOL didConvert = [scanner scanHexInt: &sha1[i]];
			NSParameterAssert( didConvert );
		}
	}
	else {
		for (i = 0; i < 5; ++i) {
			NSScanner *scanner = [NSScanner scannerWithString:[sha1String substringWithRange: NSMakeRange(8 * i, 8)]];
			BOOL didConvert = [scanner scanHexInt: &sha1[i]];
			NSParameterAssert( didConvert );
		}
	}
}

- (BOOL)_computeSHA1ForData: (NSData *)data sha1: (uint32_t *)sha1 error: (NSError **)outError
{
	NSString *path = [self temporaryFilePath];
	if (![data writeToFile: path options: 0 error: outError]) return NO;
	
	@try {
		NSTask *sha1Task = [[[NSTask alloc] init] autorelease];
		NSPipe *outputPipe = [NSPipe pipe];
		NSFileHandle *outHandle = [outputPipe fileHandleForReading];
		
		[sha1Task setLaunchPath:@"/usr/bin/openssl"];
		[sha1Task setStandardOutput: outputPipe];
		
		NSArray *arguments = [NSArray arrayWithObjects: @"dgst", @"-sha1", path, nil];
		[sha1Task setArguments:arguments];
		[sha1Task launch];
		
		// block until task completes
		while ([sha1Task isRunning])
			[NSThread sleepUntilDate:[NSDate distantPast]];
		
		if (0 != [sha1Task terminationStatus]) {
			[self createReadError: outError descriptionKey: @"Sha1Failed" format: [NSString stringWithFormat: @"%d", [sha1Task terminationStatus]]];
			return NO;
		}
		
		NSData *outData = [outHandle readDataToEndOfFile];
		NSParameterAssert( outData && 0 != [outData length] );
		NSMutableString *contents = [[[NSMutableString alloc] initWithData: outData encoding: NSUTF8StringEncoding] autorelease];
		NSString *sha1String = [[contents componentsSeparatedByString: @" "] lastObject];
		sha1String = [sha1String stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
		NSParameterAssert( [sha1String length] == 40 );
		
		[self _sha1String: sha1String toValues: sha1];
	}
	
	@finally {
		NSError *dummyError;
		[[NSFileManager defaultManager] removeItemAtPath: path error: &dummyError];
	}
	
	return YES;
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
	NSString *errorString = nil;
	NSDictionary *plist = [NSPropertyListSerialization propertyListFromData: data mutabilityOption: NSPropertyListImmutable format: NULL errorDescription: &errorString];
	if (!plist) {
		[self createReadError: outError descriptionKey: @"ReadFailBadPList" format: errorString];
		return NO;
	}
	
	// - AuthVersion
	
	NSString *authVersion = [plist valueForKey: @"AuthVersion"];
	[self appendContents: [NSString stringWithFormat: @"AuthVersion: %@\n", authVersion]];
	
	// - AuthData

	NSData *authData = [plist valueForKey: @"AuthData"];
	static NSData *sExpectedData = nil;
	if (!sExpectedData) {
		char const expectedData[] = {'F', 'o', 'r', 't', 'y', ' ', 'T', 'w', 'o'};
		sExpectedData = [[NSData alloc] initWithBytes: expectedData length: sizeof(expectedData) / sizeof(*expectedData)];
	}
	
	[self appendContents: @"AuthData"];
	if ([authData isEqualToData: sExpectedData])
		[self appendContents: @" (as expected): Forty Two\n"];
	else
		[self appendContents: [NSString stringWithFormat: @": %@\n", [authData description]]];
		
	// - AuthSignature

	uint32_t sha1[5] = {0, 0, 0, 0, 0};
	NSData *authSignature = [plist valueForKey: @"AuthSignature"];
	[self appendContents: @"AuthSignature"];
	if (20 == [authSignature length]) {
		[self appendContents: @" (SHA-1): "];
		
		NSString *sha1String = [authSignature description];
		[self _sha1String: sha1String toValues: sha1];
		
		NSString *fullSHA1String = [NSString stringWithFormat: @"%08x%08x%08x%08x%08x", sha1[0], sha1[1], sha1[2], sha1[3], sha1[4]];
		[self appendContents: [NSString stringWithFormat: @"%@\n", fullSHA1String]];
	}
	else {
		[self appendContents: @": "];
		[self appendContents: [NSString stringWithFormat: @"%@\n", [authSignature description]]];
	}
	
	// - Data
	
	NSData *manifestData = [plist valueForKey: @"Data"];
	if (!manifestData) {
		[self appendContents: NSLocalizedString(@"NoManifestData", nil)];
		return YES;
	}
	
	uint32_t foundSHA1[5] = {0, 0, 0, 0, 0};
	BOOL didSha1 = [self _computeSHA1ForData: manifestData sha1: foundSHA1 error: outError];
	if (!didSha1) return NO;
	
	if (foundSHA1[0] == sha1[0] && foundSHA1[1] == sha1[1] && foundSHA1[2] == sha1[2] && foundSHA1[3] == sha1[3] && foundSHA1[4] == sha1[4])
		[self appendContents: @"Signature is valid\n\n"];
	else
		[self appendContents: @"Signature is INVALID\n\n"];
	
	return [self appendData: manifestData error: outError];
}

@end
